
from typing import Dict, List, Tuple, Optional
from playwright.sync_api import sync_playwright, Route, Request
from playwright.sync_api import Page, Response
from playwright.sync_api import sync_playwright
arr = []
def on_response(resp):
    # print(resp)
    # 只要 URL 里含有这段路径，就认为是 detail 包
    if "/aweme/v1/web/aweme/detail/" in resp.url:
        # print("✅ 抓到 detail:", resp)
        try:
            data = resp.json()
            arr.append(data)
            # print(data)
            # print("标题:", data["aweme_detail"]["desc"])
        except Exception:
            print("⚠️ 返回的不是 JSON:", resp.text()[:200])

def wait_for_detail_json(page: Page,  timeout: int = 12000) -> dict:
    """
    等待抖音视频页发出的 detail 接口请求，并返回 JSON 数据。

    参数:
        page: Playwright Page 对象
        aweme_id: 抖音视频的 aweme_id
        timeout: 超时时间（毫秒），默认 12 秒

    返回:
        dict: detail 接口返回的 JSON 数据
    """

    def is_detail_url(u: str) -> bool:
        return "/aweme/v1/web/aweme/detail/" in u

    # 用 wait_for_event 替代 wait_for_response，兼容性更好
    resp: Response = None
    try:
        resp = page.wait_for_event(
            "response",
            predicate=lambda r: is_detail_url(r.url),
            timeout=timeout
        )
    except Exception:
        # 如果首屏没触发，滚动后再等一次
        page.mouse.wheel(0, 1200)
        resp = page.wait_for_event(
            "response",
            predicate=lambda r: is_detail_url(r.url),
            timeout=timeout
        )

    # 解析响应
    try:
        return resp.json()
    except Exception:
        text = resp.text()
        raise RuntimeError(
            f"⚠️ 捕获到 detail 响应，但不是 JSON。\nURL={resp.url}\n状态码={resp.status}\n片段={text[:200]}")

# -------- 拦截策略（精简资源以“轻跑”页面 JS）--------
BLOCK_RESOURCE_TYPES = {"image", "media", "font", "stylesheet", "other",
                        "mon.zijieapi.com",  # 监控/埋点 batch
                        "mcs.zijieapi.com",  # webid/list 反复上报
                        "privacy.zijieapi.com",  # CMP/隐私同意
                        "lf-security.bytegoofy.com",  # 安全/指纹 SDK
                        "lf-rc1.yhgfb-cn-static.com"  # 验证码中心（如未触发尽量不拉）
                        }  # 直接中止这些类型
ALLOW_HOST_SUFFIXES = (".douyin.com", 'https://www.douyin.com/aweme/v1/web/aweme/detail/'
                       ,
                       "douyin.com",  # 主站：文档 + detail XHR
                       "douyinstatic.com",  # 前端框架/播放器分片
                       "bytednsdoc.com",  # 有时静态资源会走这里
                       "bytescm.com", "bytetos.com"  # 字节系静态 CDN（框架/播放器）
                       )  # 只放行抖音主域；其余域名默认拦截（可按需扩展）

SAFE_BLOCK_TYPES = {"image", "media", "font", "stylesheet", "other"}  # 渲染大头，直接拦
BLOCK_HOSTS = {
    "mon.zijieapi.com",  # 监控/埋点 batch
    "mcs.zijieapi.com",  # webid/list 反复上报
    "privacy.zijieapi.com",  # CMP/隐私同意
    "lf-security.bytegoofy.com",  # 安全/指纹 SDK
    "lf-rc1.yhgfb-cn-static.com"  # 验证码中心（如未触发尽量不拉）
}
ALLOW_HOST_SUFFIXES = (
    "douyin.com",  # 主站：文档 + detail XHR
    "douyinstatic.com",  # 前端框架/播放器分片
    "bytednsdoc.com",  # 有时静态资源会走这里
    "bytescm.com", "bytetos.com"  # 字节系静态 CDN（框架/播放器）
)


# BLOCK_RESOURCE_TYPES = {}


def route_handler(route: Route, request: Request):
    """
    路由拦截规则：
    - 必须允许页面的 HTML 与关键 JS/XHR 执行（否则无法生成 a_bogus）；
    - 尽可能中止图片/媒体/字体/样式等重资源；
    - 仅放行目标主域，第三方域名直接拦截，减少体量与指纹噪音。
    """
    url = request.url
    host = (request.url.split("/")[2]).split(":")[0] if "://" in url else ""
    rtype = request.resource_type

    # 1) detail 必须放行（最关键）
    if "/aweme/v1/web/aweme/detail" in url:
        return route.continue_()

    # 2) 按类型拦大头
    if rtype in SAFE_BLOCK_TYPES:
        return route.abort()

    # 3) 样式表可“空响应”加速 onload（避免长等）
    if rtype == "stylesheet":
        return route.fulfill(status=200, body="/* noop */", content_type="text/css")

    # 4) 按域名拦监控/隐私/指纹等（不影响拿 detail）
    if host in BLOCK_HOSTS:
        return route.abort()

    # 5) 白名单域名正常放行（主站 + 核心静态）
    if host.endswith(ALLOW_HOST_SUFFIXES):
        return route.continue_()

    # if '.js' in url:
    #     return route.abort()

    # 6) 其他第三方：保守放行一次；若想更狠可改成 abort()
    return route.continue_()



def capture_one_aweme(url):
    """
    打开视频页 -> 等待 detail XHR -> 读取 JSON -> 立即收手
    返回内容：
      {
        "url": detail_完整签名_URL,
        "status": 响应码,
        "data": JSON字典,
        "cookies": { ... }
      }
    """
    with sync_playwright() as p:
        # 1) 启动内置浏览器（无头），开销更小；需要观察可改为 headless=False

        browser = p.chromium.launch(headless=False)

        context = browser.new_context()

        # context.route("**/*", route_handler)

        page = context.new_page()

        page.on('response', on_response)
        # 3) 直接访问视频页（通常即可生成 cookie + 触发 detail XHR）

        # video_url = f"https://www.douyin.com/video/{aweme_id}"



        page.goto(url)




        # time.sleep(200000)
        # time.sleep(5)

        # 4) 等待 detail 响应；若首屏未触发，轻微滚动后再等一次
        resp = None
        try:
            resp = wait_for_detail_json(page, 800)
        except Exception:
            # 有些情况下首屏不打 detail；滚动一下再等
            try:
                page.mouse.wheel(0, 1200)
                resp = wait_for_detail_json(page, 800)
            except Exception:
                page.close()
                context.close()
                browser.close()
                # raise RuntimeError(f"⏱️ 未捕获到 detail 接口响应（aweme_id={aweme_id}）。可能被风控或加载偏慢。")


        page.close()

        # print(resp.json())
        return arr[0]

def _is_h265(br_item: Dict) -> bool:
    # 抖音里 is_h265==1 基本就是 H.265/HEVC；is_bytevc1==1 是 ByteVC1（抖音自家编解码）
    return bool(br_item.get("is_h265"))


def _codec_name(br_item: Dict) -> str:
    if br_item.get("is_h265"):
        return "H.265/HEVC"
    if br_item.get("is_bytevc1"):
        return "ByteVC1"
    return "H.264/AVC"


def list_candidates(aweme_detail: Dict) -> List[Dict]:
    """从 aweme_detail 中取出所有清晰度候选项，补充常用字段以便排序与展示"""
    video = aweme_detail.get("aweme_detail", aweme_detail).get("video", {})
    br_list = video.get("bit_rate") or []
    out = []
    for br in br_list:
        pa = br.get("play_addr", {})
        urls = pa.get("url_list") or []
        if not urls:
            continue
        out.append({
            "height": int(pa.get("height") or 0),
            "width": int(pa.get("width") or 0),
            "fps": int(br.get("FPS") or 0),
            "bitrate_kbps": int(br.get("bit_rate") or 0) // 1000 if br.get("bit_rate") else 0,
            "gear_name": br.get("gear_name") or "",
            "quality_type": br.get("quality_type"),
            "hdr": bool(br.get("HDR_bit")),
            "codec": _codec_name(br),
            "is_h265": _is_h265(br),
            "url_list": urls,
            "url": urls[0],  # 默认给第一个
        })
    # 补充：有些视频只给了 play_addr_h264 或 play_addr_265，可作为“备胎”
    for key, codec_label in [("play_addr_h264", "H.264/AVC"), ("play_addr_265", "H.265/HEVC")]:
        pa = video.get(key) or {}
        urls = pa.get("url_list") or []
        if urls:
            out.append({
                "height": int(pa.get("height") or 0),
                "width": int(pa.get("width") or 0),
                "fps": 30,  # 常见默认
                "bitrate_kbps": int((video.get("meta") or "").isdigit() and video["meta"]) or 0,
                "gear_name": key,
                "quality_type": None,
                "hdr": False,
                "codec": codec_label,
                "is_h265": (key == "play_addr_265"),
                "url_list": urls,
                "url": urls[0],
            })
    return out


# 挑选最佳画质
def pick_best_url(
        aweme_detail: Dict,
        prefer_h264: bool = True
) -> Tuple[Optional[Dict], List[Dict]]:
    """
    返回 (best_item, all_items)。
    规则（可按需要调整）：
      1) 分辨率高者优先（height 2160 > 1440 > 1080 > 720 > 540…）
      2) 同分辨率：FPS 高者优先
      3) 同分辨率 & 同 FPS：码率高者优先
      4) 同上都相同：按编码偏好（默认优先 H.264，其次 ByteVC1/H.265）
    """
    items = list_candidates(aweme_detail)
    if not items:
        return None, []

    def codec_rank(item):
        # 偏好顺序：H.264(0) 最优 → ByteVC1(1) → H.265(2)（你也可以改成相反）
        if prefer_h264:
            return 0 if item["codec"].startswith("H.264") else (1 if "ByteVC1" in item["codec"] else 2)
        else:
            # 若想优先 H.265，把这段改成 H.265 最小排名
            return 0 if item["codec"].startswith("H.265") else (1 if "ByteVC1" in item["codec"] else 2)

    # 排序：分辨率↓, FPS↓, 码率↓, 编码偏好
    items_sorted = sorted(
        items,
        key=lambda x: (
            x["height"],
            x["fps"],
            x["bitrate_kbps"],
            -codec_rank(x)  # 偏好越小越好，所以取负号让它也降序
        ),
        reverse=True
    )
    return items_sorted[0], items_sorted

# 取id
import requests

def get_url(s):
    s = s.split('https://')[1]
    s='https://' + s

    s = requests.get(s).url

    return s

# 处理字典，找出最优结果
def give(data):
    re = {"ok": True,
          "videos": [
              # {
              # "id": "1", "title": "海边日出", "author": "用户A",
              # "duration": "15s", "publish_time": "2025-09-01",
              # "cover_url": "/static/picture/demo_cover.jpg",
              # "page_url": "https://www.douyin.com/video/xxxx",
              # "play_url": "https://www.douyin.com/video/xxxx/play",
              # "download_url": "https://example.com/download/xxxx"
              # }
          ]}
    best, all_items = pick_best_url(data, prefer_h264=True)
    # data = data
    aweme = data.get("aweme_detail", {})

    if not best:
        print("没有找到可用的视频直链。")
    else:
        print("最高画质（自动挑选）:")
        print(f"  分辨率: {best['width']}x{best['height']}, FPS: {best['fps']}, 码率: {best['bitrate_kbps']} kbps")
        print(f"  编码: {best['codec']}, HDR: {best['hdr']}, 源: {best['gear_name']}")
        print(f"  直链: {best['url']}\n")

        # print("所有可用清晰度候选：")
        for i, it in enumerate(all_items, 1):
            if it['hdr']:
                re['videos'].append({'id': str(i),
                                     'title': f"{it['width']}x{it['height']}",
                                     'author': f"{it['fps']}fps",
                                     'duration': f"{it['bitrate_kbps']} kbps",
                                     'publish_time': f"{it['codec']} | {it['gear_name']}",
                                     'play_url': f"{it['url']}",
                                     'download_url': f"{it['url']}",
                                     'url': f"{it['url']}",
                                     'cover_url': '',
                                     'hdr':f"{it['hdr']}",
                                     'codec': f"{it['codec']}"
                                     })

            if not it['hdr']:
                re['videos'].append({'id': str(i),
                                     'title': f"{it['width']}x{it['height']}",
                                     'author': f"{it['fps']}fps",
                                     'duration': f"{it['bitrate_kbps']} kbps",
                                     'publish_time': f"{it['codec']} | {it['gear_name']}",
                                     'play_url': f"{it['url']}",
                                     'download_url': f"{it['url']}",
                                     'url': f"{it['url']}",
                                     'cover_url': '',
                                     'hdr': f"{it['hdr']}",
                                     'codec': f"{it['codec']}"
                                     })
                return re





def main(s):
    url = get_url(s)
    res = None
    while True:
        try:
            res = capture_one_aweme(url)


            break
        except Exception as e:
            # print(f"❌ 失败：{e}")
            continue
    return give(res)


if __name__ == "__main__":
    s = '1.25 复制打开抖音，看看【Kenny的作品】“让你身边的朋友送你”# 迈凯伦  https://v.douyin.com/Z-shGx4xUoc/ 05/17 XMJ:/ C@u.Sl '
    main(s)
